/*
 * Created by zhangzxiang91@gmail.com on 2021/09/23.
 */
package com.perfma.util.service;

import com.perfma.util.exception.StorageException;
import com.perfma.util.model.*;
import com.perfma.util.tool.DateUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;

import java.io.*;
import java.util.Date;
import java.util.UUID;
import java.util.zip.CRC32;

/**
 * @author zhangzxiang91@gmail.com
 * @date 2021/09/23.
 */
public abstract class AbstractStorageService implements StorageService {

    private final String storageType;

    public AbstractStorageService(StorageProperties properties) {
        storageType = properties.getType().toUpperCase();
    }

    @Override
    public String upload(UploadRequest request) throws StorageException {
        checkUploadRequest(request);

        long start = System.currentTimeMillis();
//        log.info("[{}] - 开始上传文件: request={}", storageType, request);
        String originalName = getUploadFileName(request);
        try (UploadInputStream is = getUploadInputStream(request)) {
            String fileKey = doUpload(request.getFileKey(), is, is.getObjectSize(), originalName);
//            log.info("[{}] - 上传文件成功: fileKey={} request={} 耗时: {}", storageType, request, fileKey, elapsedTime(start));
            return fileKey;
        } catch (Throwable e) {
//            log.error("[{}] - 上传文件失败: request={} error: {}", storageType, request, e.getMessage());
            e.printStackTrace();
            throw new StorageException(e);
        }
    }

    @Override
    public String appendUpload(AppendUploadRequest request) throws StorageException {
        checkUploadRequest(request);

        long start = System.currentTimeMillis();
//        log.info("[{}] - 开始追加上传文件: request={}", storageType, request);
        String originalName = getUploadFileName(request);
        try (UploadInputStream is = getUploadInputStream(request)) {
            String fileKey = doAppendUpload(request.getFileKey(), is, is.getObjectSize(), originalName);
//            log.info("[{}] - 追加上传文件成功: request={} 耗时: {}", storageType, request, elapsedTime(start));
            return fileKey;
        } catch (Throwable e) {
//            log.error("[{}] - 追加上传文件失败: request={} error: {}", storageType, request, e.getMessage());
            e.printStackTrace();
            throw new StorageException(e);
        }
    }

    @Override
    public void truncateUpload(TruncateUploadRequest request) throws StorageException {
        checkUploadRequest(request);
        if (StringUtils.isBlank(request.getFileKey())) {
            throw new IllegalArgumentException("文件fileKey不能为空");
        }

        long start = System.currentTimeMillis();
//        log.info("[{}] - 开始覆盖上传文件: request={}", storageType, request);
        String originalName = getUploadFileName(request);
        try (UploadInputStream is = getUploadInputStream(request)) {
            doTruncateUpload(request.getFileKey(), is, is.getObjectSize(), originalName);
//            log.info("[{}] - 覆盖上传文件成功: request={} 耗时: {}", storageType, request, elapsedTime(start));
        } catch (Throwable e) {
//            log.error("[{}] - 覆盖上传文件失败: request={} error: {}", storageType, request, e.getMessage());
            e.printStackTrace();
            throw new StorageException(e);
        }
    }

    @Override
    public long download(DownloadRequest request) throws StorageException {
        checkDownloadRequest(request);

        long start = System.currentTimeMillis();
//        log.info("[{}] - 开始下载文件: request={}", storageType, request);
        try {
            if (getFileInfo(request.getFileKey()) == null) {
                throw new IllegalStateException("文件(fileKey='\" + request.getFileKey() + \"')不存在");
            }

            long downloadBytes;
            if (request.getOutputFile() != null) {
                FileUtils.forceMkdirParent(request.getOutputFile());
                try (FileOutputStream fos = new FileOutputStream(request.getOutputFile())) {
                    downloadBytes = doDownload(request.getFileKey(), fos, request.getOffset(), request.getLength());
                }
            } else {
                downloadBytes = doDownload(request.getFileKey(), request.getOutputStream(), request.getOffset(), request.getLength());
            }
//            log.info("[{}] - 下载文件成功: downloadBytes={} request={} 耗时: {}", storageType, SizeUtils.convert(downloadBytes), request,
//                    elapsedTime(start));
            return downloadBytes;
        } catch (Throwable e) {
//            log.error("[{}] - 下载文件失败: request={} error: {}", storageType, request, e.getMessage());
            e.printStackTrace();
            throw new StorageException(e);
        }
    }

    @Override
    public void deleteFile(String fileKey) throws StorageException {
        if (StringUtils.isBlank(fileKey)) {
            throw new IllegalArgumentException("文件fileKey不能为空");
        }

        try {
            doDeleteFile(fileKey);
//            log.info("[{}] - 删除文件成功: fileKey={} ", storageType, fileKey);
        } catch (Exception e) {
//            log.error("[{}] - 删除文件失败: fileKey={} error: {}", storageType, fileKey, e.getMessage());
            e.printStackTrace();
            throw new StorageException(e);
        }
    }

    @Override
    public FileInfo getFileInfo(String fileKey) throws StorageException {
        if (StringUtils.isBlank(fileKey)) {
            throw new IllegalArgumentException("文件fileKey不能为空");
        }

        try {
            return doGetFileInfo(fileKey);
        } catch (Exception e) {
//            log.error("[{}] - 获取文件信息失败: fileKey={} error: {}", storageType, fileKey, e.getMessage());
            e.printStackTrace();
            throw new StorageException(e);
        }
    }

    protected abstract String doUpload(String fileKey, UploadInputStream stream, long objectSize, String originalName) throws Exception;

    protected abstract String doAppendUpload(String fileKey, UploadInputStream stream, long objectSize, String originalName) throws Exception;

    protected abstract void doTruncateUpload(String fileKey, UploadInputStream stream, long objectSize, String originalName) throws Exception;

    protected abstract long doDownload(String fileKey, OutputStream os, Long offset, Long length) throws Exception;

    protected abstract void doDeleteFile(String fileKey) throws Exception;

    protected abstract FileInfo doGetFileInfo(String fileKey) throws Exception;

    private void checkUploadRequest(UploadRequest request) {
        if (request == null) {
            throw new IllegalArgumentException("上传请求不能为空");
        }
        if (request.getUploadFile() == null && request.getUploadStream() == null && request.getUploadBytes() == null) {
            throw new IllegalArgumentException("上传的文件资源为空");
        }
        if (request.getUploadFile() != null) {
            if (!request.getUploadFile().exists()) {
                throw new IllegalArgumentException("上传的文件不存在");
            }
            if (request.getUploadStream() != null || request.getUploadBytes() != null) {
                throw new IllegalArgumentException("只能设置一种上传文件类型");
            }
        } else {
            if (request.getUploadStream() != null && request.getUploadBytes() != null) {
                throw new IllegalArgumentException("只能设置一种上传文件类型");
            }
            if (request.getUploadStream() != null) {
                try {
                    int streamLength = request.getUploadStream().available();
                    // InputStream只支持最大Integer.MAX_VALUE(2G)的流，如果超出了只能使用File的方式
                    if (streamLength == Integer.MAX_VALUE) {
                        throw new IllegalArgumentException("输入流长度超过最大 " + Integer.MAX_VALUE + " 限制, 请检查");
                    }
                } catch (IOException e) {
                    throw new IllegalArgumentException("输入流读取异常" + e.getMessage());
                }
            }
        }
    }

    private void checkDownloadRequest(DownloadRequest downloadRequest) {
        if (downloadRequest == null) {
            throw new IllegalArgumentException("下载请求不能为空");
        }
        if (StringUtils.isBlank(downloadRequest.getFileKey())) {
            throw new IllegalArgumentException("文件fileKey不能为空");
        }
        if (downloadRequest.getOffset() != null && downloadRequest.getLength() != null) {
            if (downloadRequest.getOffset() < 0) {
                throw new IllegalArgumentException("下载起始位置不能小于0");
            }
            if (downloadRequest.getLength() <= 0) {
                throw new IllegalArgumentException("下载长度必须大于0");
            }
        }
        if (downloadRequest.getOutputFile() == null && downloadRequest.getOutputStream() == null) {
            throw new IllegalArgumentException("没有设置下载保存文件或下载输出流");
        }
        if (downloadRequest.getOutputFile() != null && downloadRequest.getOutputStream() != null) {
            throw new IllegalArgumentException("不能同时设置下载保存文件和下载输出流");
        }
    }

    /**
     * 生成文件key
     */
    protected String genFileKey() {
        return DateFormatUtils.format(new Date(), "yyyy/MM/dd/") + UUID.randomUUID().toString().replaceAll("-", "");
    }

    /**
     * 根据字符串获取CRC32的值
     */
    protected long getCRC32(String value) {
        CRC32 crc32 = new CRC32();
        crc32.update(value.getBytes());
        return crc32.getValue();
    }

    private String elapsedTime(long startTime) {
        return DateUtils.toDurationString(System.currentTimeMillis() - startTime);
    }

    private String getUploadFileName(UploadRequest request) {
        if (request.getUploadFile() != null) {
            return request.getUploadFile().getName();
        }
        return null;
    }

    private UploadInputStream getUploadInputStream(UploadRequest request) throws IOException {
        if (request.getUploadFile() != null) {
            return new UploadInputStream(request.getUploadFile());
        } else if (request.getUploadStream() != null) {
            InputStream is = request.getUploadStream();
            return new UploadInputStream(is, is.available());
        } else if (request.getUploadBytes() != null) {
            return new UploadInputStream(request.getUploadBytes());
        }
        throw new IllegalArgumentException("未设置文件上传源");
    }

    public static class UploadInputStream extends InputStream {

        private final InputStream is;
        private final long objectSize;
        private File originFile;

        public UploadInputStream(InputStream inputStream, long objectSize) {
            this.is = inputStream;
            this.objectSize = objectSize;
        }

        public UploadInputStream(File file) throws FileNotFoundException {
            this.is = new FileInputStream(file);
            this.objectSize = file.length();
            this.originFile = file;
        }

        public UploadInputStream(byte[] bytes) {
            this.is = new ByteArrayInputStream(bytes);
            this.objectSize = bytes.length;
        }

        public InputStream getInputStream() {
            return is;
        }

        public long getObjectSize() {
            return objectSize;
        }

        public File getOriginFile() {
            return originFile;
        }

        @Override
        public int read() throws IOException {
            return is.read();
        }

        @Override
        public int read(byte[] b) throws IOException {
            return is.read(b);
        }

        @Override
        public int read(byte[] b, int off, int len) throws IOException {
            return is.read(b, off, len);
        }

        @Override
        public long skip(long n) throws IOException {
            return is.skip(n);
        }

        @Override
        public int available() throws IOException {
            return is.available();
        }

        @Override
        public void close() throws IOException {
            is.close();
        }

        @Override
        public synchronized void mark(int readlimit) {
            is.mark(readlimit);
        }

        @Override
        public synchronized void reset() throws IOException {
            is.reset();
        }

        @Override
        public boolean markSupported() {
            return is.markSupported();
        }

    }
}
